/*
* MoneyBalance - Android-based calculator for tracking and balancing expenses
* Copyright (C) 2012 Ingo van Lil <inguin@gmx.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ivl.android.moneybalance;
import ivl.android.moneybalance.dao.CalculationDataSource;
import ivl.android.moneybalance.dao.DataBaseHelper;
import ivl.android.moneybalance.dao.ExpenseDataSource;
import ivl.android.moneybalance.data.Currency;
import ivl.android.moneybalance.data.Calculation;
import ivl.android.moneybalance.data.Expense;
import ivl.android.moneybalance.data.Person;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.view.WindowCompat;
import android.support.v7.app.ActionBarActivity;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ImageView;
import android.widget.TextView;
public class ExpenseListActivity extends ActionBarActivity implements OnChildClickListener {
public static final String PARAM_CALCULATION_ID = "calculationId";
private long calculationId;
private final DataBaseHelper dbHelper = new DataBaseHelper(this);
private final CalculationDataSource calculationDataSource = new CalculationDataSource(dbHelper);
private ExpenseDataSource expenseDataSource;
private static final int ITEM_DELETE = 0;
private ExpenseAdapter adapter;
private class ExpenseAdapter extends BaseExpandableListAdapter {
private boolean groupByPerson = true;
private Calculation calculation;
private final Map<Person, List<Expense>> expensesByPerson = new HashMap<>();
private final Set<Calendar> dates = new TreeSet<>();
private final Map<Calendar, List<Expense>> expensesByDate = new HashMap<>();
private final LayoutInflater inflater;
private final String groupSummaryFormat = getResources().getString(R.string.expenses_summary_format);
public ExpenseAdapter(Context context) {
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setCalculation(Calculation calculation) {
this.calculation = calculation;
expenseDataSource = new ExpenseDataSource(dbHelper, calculation);
expensesByPerson.clear();
dates.clear();
expensesByDate.clear();
for (Person person : calculation.getPersons())
expensesByPerson.put(person, new ArrayList<Expense>());
for (Expense expense : calculation.getExpenses()) {
List<Expense> byPersonList = expensesByPerson.get(expense.getPerson());
byPersonList.add(expense);
Calendar date = expense.getDate();
List<Expense> byDateList = expensesByDate.get(date);
if (byDateList == null) {
byDateList = new ArrayList<>();
expensesByDate.put(date, byDateList);
dates.add(date);
}
byDateList.add(expense);
}
notifyDataSetChanged();
}
public void setGroupByPerson(boolean groupByPerson) {
this.groupByPerson = groupByPerson;
notifyDataSetChanged();
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
@Override
public int getGroupCount() {
if (calculation == null)
return 0;
else if (groupByPerson)
return calculation.getPersons().size();
else
return dates != null ? dates.size() : 0;
}
@Override
public long getGroupId(int groupPosition) {
if (groupByPerson) {
Person person = (Person) getGroup(groupPosition);
return person.getId();
} else {
Calendar date = (Calendar) getGroup(groupPosition);
return date.getTimeInMillis();
}
}
@Override
public Object getGroup(int groupPosition) {
if (groupByPerson) {
return calculation.getPersons().get(groupPosition);
} else {
return dates.toArray()[groupPosition];
}
}
private class GroupViewHolder {
public TextView nameView;
public TextView summaryView;
public ImageView addButton;
}
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
View view = convertView;
GroupViewHolder holder;
if (view == null) {
view = inflater.inflate(R.layout.expense_list_group_row, parent, false);
holder = new GroupViewHolder();
holder.nameView = (TextView) view.findViewById(android.R.id.text1);
holder.summaryView = (TextView) view.findViewById(android.R.id.text2);
holder.addButton = (ImageView) view.findViewById(R.id.add_button);
view.setTag(holder);
} else {
holder = (GroupViewHolder) view.getTag();
}
List<Expense> expenses;
if (groupByPerson) {
Person person = (Person) getGroup(groupPosition);
holder.nameView.setText(person.getName());
expenses = expensesByPerson.get(person);
} else {
Calendar date = (Calendar) getGroup(groupPosition);
DateFormat format = DateFormat.getDateInstance();
holder.nameView.setText(format.format(date.getTime()));
expenses = expensesByDate.get(date);
}
int count = 0;
double total = 0;
for (Expense expense : expenses) {
count++;
total += expense.getExchangedAmount();
}
if (count == 0) {
holder.summaryView.setText(R.string.no_expenses);
} else {
String totalStr = calculation.getMainCurrency().getCurrencyHelper().format(total);
String summary = String.format(groupSummaryFormat, count, totalStr);
holder.summaryView.setText(summary);
}
holder.addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (groupByPerson) {
Person person = (Person) getGroup(groupPosition);
addExpenseForPerson(person.getId());
} else {
Calendar date = (Calendar) getGroup(groupPosition);
addExpenseForDate(date);
}
}
});
return view;
}
@Override
public int getChildrenCount(int groupPosition) {
if (groupByPerson) {
Person person = (Person) getGroup(groupPosition);
List<Expense> list = expensesByPerson.get(person);
return list.size();
} else {
Calendar date = (Calendar) getGroup(groupPosition);
List<Expense> list = expensesByDate.get(date);
return list.size();
}
}
@Override
public long getChildId(int groupPosition, int childPosition) {
Expense expense = (Expense) getChild(groupPosition, childPosition);
return expense.getId();
}
@Override
public Object getChild(int groupPosition, int childPosition) {
if (groupByPerson) {
Person person = (Person) getGroup(groupPosition);
List<Expense> list = expensesByPerson.get(person);
return list.get(childPosition);
} else {
Calendar date = (Calendar) getGroup(groupPosition);
List<Expense> list = expensesByDate.get(date);
return list.get(childPosition);
}
}
private class ChildViewHolder {
public TextView titleView;
public TextView amountView;
public TextView exchangedView;
public TextView details1View;
public TextView details2View;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
View view = convertView;
ChildViewHolder holder;
if (view == null) {
view = inflater.inflate(R.layout.expense_row, parent, false);
holder = new ChildViewHolder();
holder.titleView = (TextView) view.findViewById(R.id.expense_title);
holder.amountView = (TextView) view.findViewById(R.id.expense_amount);
holder.exchangedView = (TextView) view.findViewById(R.id.expense_exchanged_amount);
holder.details1View = (TextView) view.findViewById(R.id.expense_details_1);
holder.details2View = (TextView) view.findViewById(R.id.expense_details_2);
view.setTag(holder);
} else {
holder = (ChildViewHolder) view.getTag();
}
Expense expense = (Expense) getChild(groupPosition, childPosition);
Currency currency = expense.getCurrency();
CurrencyHelper currencyHelper = currency.getCurrencyHelper();
holder.titleView.setText(expense.getTitle());
holder.amountView.setText(currencyHelper.format(expense.getAmount()));
if (expense.getCurrency().equals(calculation.getMainCurrency())) {
holder.exchangedView.setVisibility(View.GONE);
} else {
CurrencyHelper mainCurrencyHelper = calculation.getMainCurrency().getCurrencyHelper();
double exchanged = expense.getExchangedAmount();
holder.exchangedView.setVisibility(View.VISIBLE);
holder.exchangedView.setText(mainCurrencyHelper.format(exchanged));
}
if (groupByPerson) {
DateFormat format = DateFormat.getDateInstance();
holder.details1View.setText(format.format(expense.getDate().getTime()));
} else {
Person person = expense.getPerson();
holder.details1View.setText(person.getName());
}
if (expense.isUnevenSplit()) {
List<Person> persons = calculation.getPersons();
List<Double> shares = expense.getShares(persons);
StringBuilder msg = new StringBuilder();
for (int i = 0; i < persons.size(); i++) {
if (shares.get(i) > 0) {
Person person = persons.get(i);
if (msg.length() > 0)
msg.append("; ");
String shareStr = currencyHelper.format(shares.get(i));
msg.append(String.format("%s: %s", person.getName(), shareStr));
}
}
holder.details2View.setVisibility(View.VISIBLE);
holder.details2View.setText(msg);
} else {
holder.details2View.setVisibility(View.GONE);
}
return view;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR);
setContentView(R.layout.expense_list);
Intent intent = getIntent();
calculationId = intent.getLongExtra(PARAM_CALCULATION_ID, -1);
ExpandableListView listView = (ExpandableListView) findViewById(R.id.expense_list);
adapter = new ExpenseAdapter(this);
listView.setAdapter(adapter);
listView.setOnChildClickListener(this);
registerForContextMenu(listView);
setContentView(listView);
refresh();
}
private void refresh() {
Calculation calculation = calculationDataSource.get(calculationId);
setTitle(calculation.getTitle());
adapter.setCalculation(calculation);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.expense_list_options, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent intent;
switch (item.getItemId()) {
case R.id.new_expense:
addExpense();
case R.id.group_by_person:
adapter.setGroupByPerson(true);
return true;
case R.id.group_by_date:
adapter.setGroupByPerson(false);
return true;
case R.id.manage_currencies:
intent = new Intent(this, ManageCurrenciesActivity.class);
intent.putExtra(ManageCurrenciesActivity.PARAM_CALCULATION_ID, calculationId);
startActivity(intent);
return true;
case R.id.calcluation_summary:
intent = new Intent(this, SummaryActivity.class);
intent.putExtra(SummaryActivity.PARAM_CALCULATION_ID, calculationId);
startActivity(intent);
return true;
case R.id.export_calculation:
CsvExporter.export(calculationDataSource.get(calculationId), this);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
Expense expense = (Expense) adapter.getChild(groupPosition, childPosition);
Intent intent = new Intent(this, ExpenseEditorActivity.class);
intent.putExtra(ExpenseEditorActivity.PARAM_CALCULATION_ID, expense.getCalculation().getId());
intent.putExtra(ExpenseEditorActivity.PARAM_EXPENSE_ID, expense.getId());
startActivity(intent);
return true;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (v.getId() == R.id.expense_list) {
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
int group = ExpandableListView.getPackedPositionGroup(info.packedPosition);
int child = ExpandableListView.getPackedPositionChild(info.packedPosition);
if (child != -1) {
Expense expense = (Expense) adapter.getChild(group, child);
menu.setHeaderTitle(expense.getTitle());
menu.add(0, ITEM_DELETE, 0, R.string.menu_delete);
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (item.getItemId() == ITEM_DELETE) {
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item.getMenuInfo();
int group = ExpandableListView.getPackedPositionGroup(info.packedPosition);
int child = ExpandableListView.getPackedPositionChild(info.packedPosition);
if (child != -1) {
Expense expense = (Expense) adapter.getChild(group, child);
expenseDataSource.delete(expense.getId());
refresh();
}
} else {
return false;
}
return true;
}
@Override
protected void onPause() {
super.onPause();
dbHelper.close();
}
@Override
protected void onResume() {
refresh();
super.onResume();
}
private void addExpenseForPerson(long personId) {
Intent intent = new Intent(this, ExpenseEditorActivity.class);
intent.putExtra(ExpenseEditorActivity.PARAM_CALCULATION_ID, calculationId);
intent.putExtra(ExpenseEditorActivity.PARAM_PERSON_ID, personId);
startActivity(intent);
}
private void addExpenseForDate(Calendar date) {
Intent intent = new Intent(this, ExpenseEditorActivity.class);
intent.putExtra(ExpenseEditorActivity.PARAM_CALCULATION_ID, calculationId);
intent.putExtra(ExpenseEditorActivity.PARAM_DATE, date.getTimeInMillis());
startActivity(intent);
}
private void addExpense() {
Intent intent = new Intent(this, ExpenseEditorActivity.class);
intent.putExtra(ExpenseEditorActivity.PARAM_CALCULATION_ID, calculationId);
startActivity(intent);
}
}